﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Uniriotec.DC.CoordDist.Base;

namespace Uniriotec.DC.CoordDist.Base.Distribuida
{
    // Essa classe herda as funcionalidade básicas de Process, que já sabe como mandar uma mensagem para outro processo,
    // o que fazer para receber uma mensagem, seu tempo atual, etc.
    public class MutualExclusionProcess : Process
    {
        private bool doingCriticalWork = false;
        private int ackCount = 0;
        private bool requesting = false;
        private List<int> deferredReplys = new List<int>();

        private const string AckMessage = "Ack";
        private const string ReqMessage = "Req";

        /// <summary>
        /// Esse é o método que deve ser modificado de acordo com os diferentes "trabalhos"
        /// realizados pelos processos. Nesse exemplo, todos os processos são iguais.
        /// </summary>
        protected override void DoWork()
        {
            // No ciclo de trabalho de exemplo, primeiro é feito o "trabalho normal".
            // O "trabalho normal" é na verdade esperar um tempo aleatório entre 0 e 4 segundos.
            // Esse tempo aleatório faz com que a ordem em que os processos fazem seus trabalhos possa mudar.
            DoNormalWork();

            // Em seguida, o ciclo de trabalho maior é repetido 10 vezes
            int j = 0;
            while (j++ < 10)
            {
                DoNormalWork();  // Novamente, fazendo o "trabalho normal"
                AskToEnterCritical(); // Pede a todos os outros para entrar na zona crítica
                WaitToEnterCritical(); // Espera a resposta de todos os outros de que pode entrar
                DoCriticalWork(); // Faz o trabalho crítico
                ReplyDeferred(); // Responde aos pedidos que adiou
            }

            Debug("finished doing work");
        }

        private void AskToEnterCritical()
        {
            Debug("asked to enter critical");
            requesting = true; //marca que está pedindo para entrar na região crítica

            ChooseTime(); // atualiza o relógio do processo.
            // Para isso, pega o tempo mais alto que já recebeu numa mensagem e soma 1

            ackCount = 0; // reinicia o contador de respostas recebidas para 0
            SendToAll(ReqMessage); // manda pra todos a mensagem de que quer entrar na zona crítica
        }

        private void WaitToEnterCritical()
        {
            // Enquanto o número de respostas é diferente do número total de outros
            // processos, espera meio segundo e verifica novamente
            Debug("is waiting to enter critical");

            while (ackCount != NodeIDs.Count - 1)
            {
                System.Threading.Thread.Sleep(500);
            }
            // Se passou do loop, é porquê já recebeu a resposta de todos para entrar na região crítica.
        }

        private void DoCriticalWork()
        {
            //Nesse exemplo, o trabalho crítico é escrever no arquivo c:\distributed.txt uma mensagem
            //que diz qual processo escreveu.

            doingCriticalWork = true; //marca que está na região crítica

            Debug("is doing critical work");

            #region Write a line to a file (example of critical work)

            FileStream file = new FileStream(@"C:\distributed.txt", FileMode.Append, FileAccess.Write);
            StreamWriter writer = new StreamWriter(file);
            writer.WriteLine("Process " + Id + " did critical work");
            System.Threading.Thread.Sleep(1500);

            writer.Flush();
            file.Close();

            #endregion

            doingCriticalWork = false; //marca que saiu da região crítica
        }

        private void ReplyDeferred()
        {
            // Para cada outro processo, vê se há algum pedido que teve resposta adiada.
            // Se existe, manda a resposta e tira da lista de respostas adiadas.

            Debug("released lock");
            requesting = false;
            Message reply = new Message(Id, AckMessage);

            for (int i = 1; i <= TotalProcs; i++)
            {
                if (deferredReplys.Contains(i))
                {
                    deferredReplys.Remove(i);
                    SendMessage(reply, i);
                }

            }
        }

        protected override void OnMessageReceived(object sender, MessageReceivedEventArgs e)
        {
            // A qualquer momento, uma mensagem pode ser recebida pelo processo. 
            // Quando for recebida, esse método será chamado, e a variável e.Message conterá a mensagem.

            // Nesse exemplo, a função ReceiveMessage cuida de verificar se mensagem é uma requisição ou uma resposta.
            ReceiveMessage(e.Message);
        }


        private void ReceiveMessage(Message message) 
        {
            // Se a mensagem é uma resposta
            if (message.Text == AckMessage)
            {
                ackCount++; // Incrementa o contador de respostas recebidas
                Debug("received " + ackCount + " ACKs - Last ACK from " + message.ProcId);
            }
            else if (message.Text == ReqMessage) // Se é um pedido de acesso à zona crítica
            {
                //manda para a função que vai verificar se resposta imediatamente ou adia a resposta
                ReceiveReq(message);

                Debug("received Req from (Process: " + message.ProcId + ") Time: " + message.Time);
            }
        }

        private void ReceiveReq(Message message)
        {
            // A função isDeferred aplica a lógica do algorítimo para decidir se adia ou não a resposta.
            // caso o resultado seja para adiar, a função retorna true.

            bool defer = IsDeferred(message);

            if (defer) // caso seja adiado
            {
                deferredReplys.Add(message.ProcId); //adiciona na lista de respostas adiadas
                return;
            }

            SendMessage(AckMessage, Id, message.ProcId); // Caso não seja adiado, envia a resposta.
        }


        private bool IsDeferred(Message message)
        {
            // As veificações se uma resposta deve ser adiada ou não são extraídas direto do algorítmo

            if (doingCriticalWork) // Se está na zona crítica, adia a resposta.
                return true;

            // Se está pedindo para entrar na zona crítica, há duas situações onde é adiada a resposta.
            // 1. Caso o tempo do pedido do processo seja menor do que o tempo do pedido recebido agora.
            // 2. Caso os tempos sejam iguais, e o número do processo seja menor.

            // O caso 2 é somente um desempate para quando acontece dos tempos serem iguais, já que nessa
            // situação não há como saber quem pediu primeiro.
            if (requesting && (Time < message.Time || (Time == message.Time && Id < message.ProcId)))
                return true;

            //Se não está na zona crítica, nem está pedindo para entrar, ou seu pedido é mais novo
            //do que o pedido que chegou, responde imediatamente.
            return false;
        }

    }
}
